package com.dmw.demo.utils;


import com.dmw.demo.entity.DocInfo;
import com.dmw.demo.entity.Weight;
import com.dmw.demo.entity.WordCnt;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.ansj.domain.Term;
import org.ansj.splitWord.analysis.ToAnalysis;

import java.io.File;
import java.io.IOException;
import java.util.*;

/**
 * 实现了索引的数据结构
 */
@Slf4j
public class Index {

    // 正排索引（id 查 文档）
    private ArrayList<DocInfo> forwardIndex = new ArrayList<>();

    // 倒排索引（词 查 id）
    private HashMap<String, ArrayList<Weight>> invertedIndex = new HashMap<>();

    private ObjectMapper objectMapper = new ObjectMapper();

    // 创建两个锁对象（用于多线程对正排索引和倒排索引的构建进行加锁）
    private final Object locker1 = new Object();
    private final Object locker2 = new Object();

    /**
     * 给定一个 docId，在正排索引中，铲鲟文档的详细信息
     */
    public DocInfo getDocInfo(int docID) {
        return forwardIndex.get(docID);
    }

    /**
     * 给定一个词，在倒排索引中，查哪些文档和这个词关联
     */
    public List<Weight> getInverted(String term) {
        return invertedIndex.get(term);
    }

    /**
     * 往索引中新增一个文档（正排和倒排都添加）
     */
    public void addDoc(String title, String url, String content) {
        // 构建正排
        DocInfo docInfo = buildForward(title, url, content);
        // 构建倒排
        buildInverted(docInfo);
    }

    private void buildInverted(DocInfo docInfo) {
        // 统计词频
        HashMap<String, WordCnt> wordCntHashMap = new HashMap<>();
        // 1. 针对文档标题进行分词
        String title = docInfo.getTitle();
        List<Term> titleTerms = ToAnalysis.parse(title).getTerms();
        // 2. 遍历分词结果，统计每个词出现的次数
        for (Term term : titleTerms) {
            String name = term.getName();
            if (wordCntHashMap.containsKey(name)) {
                WordCnt wordCnt = wordCntHashMap.get(name);
                wordCnt.setTitleCount(wordCnt.getTitleCount() + 1);
            } else {
                WordCnt wordCnt = new WordCnt();
                wordCnt.setTitleCount(1);
                wordCntHashMap.put(name, wordCnt);
            }
        }
        // 3. 针对文档内容进行分词（使用的分词库包自动将大小写转换了）
        String content = docInfo.getContent();
        List<Term> contentTerms = ToAnalysis.parse(content).getTerms();
        // 4. 遍历分词结果，统计每个词出现的次数
        for (Term term : contentTerms) {
            String name = term.getName();
            if (wordCntHashMap.containsKey(name)) {
                WordCnt wordCnt = wordCntHashMap.get(name);
                wordCnt.setContentCount(wordCnt.getTitleCount() + 1);
            } else {
                WordCnt wordCnt = new WordCnt();
                wordCnt.setContentCount(1);
                wordCntHashMap.put(name, wordCnt);
            }
        }
        // 6. 遍历 HashMap，更新倒排索引中的结构（文档权重可以设定为标题中出现的次数*N + 内容中出现的次数）
        for (Map.Entry<String, WordCnt> entry : wordCntHashMap.entrySet()) {
            synchronized (locker2) {
                String key = entry.getKey();
                WordCnt value = entry.getValue();
                int w = value.getTitleCount() * 10 + value.getContentCount();
                Weight weight = new Weight();
                weight.setDocId(docInfo.getDocId());
                weight.setWeight(w);
                if (invertedIndex.containsKey(key)) {
                    ArrayList<Weight> weights = invertedIndex.get(key);
                    weights.add(weight);
                } else {
                    ArrayList<Weight> weights = new ArrayList<>();
                    weights.add(weight);
                    invertedIndex.put(key, weights);
                }
            }
        }
    }

    private DocInfo buildForward(String title, String url, String content) {
        DocInfo docInfo = new DocInfo();
        docInfo.setTitle(title);
        docInfo.setUrl(url);
        docInfo.setContent(content);
        synchronized (locker1) {
            docInfo.setDocId(forwardIndex.size()); // 让 docId 与存储数组中的索引对应
            forwardIndex.add(docInfo);
        }
        return docInfo;
    }

    /**
     * 把内存中的索引结构保存到磁盘中（数据量大，构建时间长，可以启动服务前就构建好并保存到磁盘中）
     */
    public void save() {
        long begin = System.currentTimeMillis();
        log.info("开始保存文件索引");
        // 1. 先判断索引对应的目录是否存在，不存在则不创建
        File indexPathFile = new File(SystemConstants.FILE_INDEX_PATH);
        if (!indexPathFile.exists()) {
            indexPathFile.mkdirs();
        }
        File forwardIndexFile = new File(SystemConstants.FILE_INDEX_PATH + SystemConstants.FILE_FORWARD_NAME);
        File invertedIndexFile = new File(SystemConstants.FILE_INDEX_PATH + SystemConstants.FILE_INVERTED_NAME);
        try {
            objectMapper.writeValue(forwardIndexFile, forwardIndex);
            objectMapper.writeValue(invertedIndexFile, invertedIndex);
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        log.info("完成保存文件索引，消耗时间：{}", (end-begin) + " ms");
    }

    /**
     * 把磁盘中的索引数据加载到内存中
     */
    public void load() {
        long begin = System.currentTimeMillis();
        log.info("开始加载索引");
        // 1. 先设置加载索引的路径
        File forwardIndexFile = new File(SystemConstants.FILE_INDEX_PATH + SystemConstants.FILE_FORWARD_NAME);
        File invertedIndexFile = new File(SystemConstants.FILE_INDEX_PATH + SystemConstants.FILE_INVERTED_NAME);
        try {
            forwardIndex = objectMapper.readValue(forwardIndexFile, new TypeReference<ArrayList<DocInfo>>() {});
            invertedIndex = objectMapper.readValue(invertedIndexFile, new TypeReference<HashMap<String, ArrayList<Weight>>>() {});
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        log.info("完成加载索引，消耗时间为：{}", (end-begin) + " ms");
    }

//    public static void main(String[] args) {
//        Index index = new Index();
//        index.load();
//        System.out.println(index.invertedIndex.size());
//    }
}
